package tp.pr2.mv;

import tp.pr2.mv.instruction.Instruction;

public class CPU {
	private Memory memoria;
	private OperandStack pila;
	private ProgramMV programa;
	private int pc, next_pc;
	private boolean parado = true;
	
	public CPU() {
		this.memoria = new Memory();
		this.pila = new OperandStack();
		this.programa = new ProgramMV();
		this.pc = 0;
		this.next_pc = 0;
		this.parado = false;
	}
	
	// M�TODOS DE PROGRAMA
	
	public void loadProgram(ProgramMV programa) {
		this.programa = programa;
	}
	
	public boolean step() {
		boolean instrStepOk = false;
		Instruction instrNow = null;
		
		this.pc = this.next_pc;
		
		instrNow = programa.getListainst(this.pc);
		if(instrNow != null)
		{
			System.out.println("Comienza la ejecución de " + instrNow.toString() +" ");
			
			if(instrNow.execute(this, this.pila)){
				instrStepOk = true;
				this.next_pc++;
				System.out.println("El estado de la maquina tras ejecutar la instrucción es: ");
				System.out.println(this.toString());
			}
			else System.out.println("Error en la ejecución de la instrucción.");
		}
		else
			this.halt();
		
	return instrStepOk;
	//Si la instruccion no es valida, el programa se queda en esa instruccion clavado y ya no avanza
	}
	
	public boolean setNextpc(int nextpc){
		boolean instrOk = true;
		if(nextpc > 0 && nextpc <= this.programa.getContadorProgramMv()) { //verificar que la posicion de salto es correcta
			this.next_pc = nextpc-1;
		}
		else instrOk = false;
		return instrOk;
	}
	
	// Métodos para controlar el programMV desde la CPU, 
	//si el programMV fuera public, no se necesitarían
	
	public OperandStack getPila() {
		return this.pila;
	}
	
	public int getPc() {
		return this.pc;
	}
	
	public Memory getMemoria() {
		return this.memoria;
	}
	
	public int getContadorProgramMvForRun(){
		return this.programa.getContadorProgramMv();
	}
	
	// INSTRUCCIONES DE PILA
	
	public boolean halt() {
		return this.parado = true;
	}
	
	public boolean getParado() {
		return this.parado;
	}
	
	public boolean dup() {
		boolean ok = true;
		if (pila.pilaVacia()) ok = false;
		else pila.push(pila.getCima());
		return ok;
	}
	
	public boolean flip() {
		int aux1, aux2;
		boolean ok = true;
		if (pila.getContador() < 2) ok = false;
		else {
			aux1 = pila.getCima();
			pila.pop();
			aux2 = pila.getCima();
			pila.pop();
			pila.push(aux1);
			pila.push(aux2);
		}
		return ok;
	}
	
	public boolean add() {
		int aux1, aux2;
		boolean ok = true;
		if (pila.getContador() < 2) ok = false;
		else {
			aux1 = pila.getCima();
			pila.pop();
			aux2 = pila.getCima();
			pila.pop();
			pila.push(aux2 + aux1);
		}
		return ok;
	}
	
	public boolean sub() {
		int aux1, aux2;
		boolean ok = true;
		if (pila.getContador() < 2) ok = false;
		else {
			aux1 = pila.getCima();
			pila.pop();
			aux2 = pila.getCima();
			pila.pop();
			pila.push(aux2 - aux1);
		}
		return ok;
	}
	
	public boolean mul() {
		int aux1, aux2;
		boolean ok = true;
		if (pila.getContador() < 2) ok = false;
		else {
			aux1 = pila.getCima();
			pila.pop();
			aux2 = pila.getCima();
			pila.pop();
			pila.push(aux2 * aux1);
		}
		return ok;
	}
	
	public boolean div() {
		int aux1, aux2;
		boolean ok = true;
		if ((pila.getContador() < 2)) ok = false;
		else {
			aux1 = pila.getCima();
			pila.pop();
			aux2 = pila.getCima();
			pila.pop();
			if (aux1 != 0) pila.push(aux2 / aux1);
			else ok = false;
		}
		return ok;
	}

	public String out() {
		char c = (char) pila.getCima(); //casting a char sobre un integer
		pila.pop();
		return "" + c;
	}
	
	// INSTRUCCIONES DE MEMORIA-PILA
	
	public boolean load(int pos) {
		boolean ok = true;
		int lugar = memoria.getMemory(pos);
		if (memoria.memoriaVacia() || lugar == -1) ok = false;
		else if (lugar > -1) pila.push(lugar);
		return ok;
	}
	
	public boolean store(int pos) {
		boolean ok = true;
		CeldaMemoria celdaEntrada;
		if (!memoria.memoriaLlena() && !pila.pilaVacia()) {
			int valor = pila.getCima();
			celdaEntrada = new CeldaMemoria(pos,valor);
			if (memoria.getContador()==0){				// Memoria vac�a
				memoria.nuevaCelda();
				memoria.setCelda(0, celdaEntrada);
				//memoria.setCelda(0, pos, valor);
			}
			else {
				int a = memoria.busquedaStore(pos);
				memoria.nuevaCelda();
				if(memoria.getCelda(a).getMemPos() == pos){	// Posici�n existente
					memoria.setCelda(a, celdaEntrada);
					//memoria.setCelda(a, pos, valor);
					memoria.setContadorDown();
				}
				else {
					if(memoria.getCelda(a).getMemPos() == -1){	//<pos	//�ltima posici�n
						memoria.setCelda(a, celdaEntrada);
					}
					else {									//this.memoria[a].getMemPos() >pos
						for(int k=memoria.getContador()-1; k>a; k--) 
							memoria.setCelda(k, memoria.getCelda(k-1));
						memoria.setCelda(a, celdaEntrada);
						//memoria.setCelda(a, pos, valor);
					}
				}
			}
			this.pila.pop();
		}
		else ok = false;
		return ok;
	}

	public String toString(){
		return "Memoria: " + this.memoria.toString() + '\n' + "Pila de operandos: "+ this.pila.toString();
	}

}
